/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
    fprintf(stdout, "Bruinbase> ");
    
    // set the command line input and start parsing user input
    sqlin = commandline;
    sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
    // SqlParser.y by bison (bison is GNU equivalent of yacc)
    
    return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
    RecordFile rf;   // RecordFile containing the table
    RecordId   rid;  // record cursor for table scanning
    
    RC     rc;
    int    key;     
    string value;
    int    count;
    int    diff;
    //------------------------------------------
	bool treeExists = false; //check if the btree is openable
	int i; //just for "for loops" counter
	int max; //the max number to keep track of
	int min; //min num to keep track of
	bool minexist = false;
	bool maxexist = false;
	bool minfirst = false; //finds out if max value is a Larger Than
	bool maxfirst = false; //finds out if max value is a Less Than
	bool minfirstLTE = false; //finds out if max value is a LTE
	bool maxfirstGTE = false; //finds out if max value is a GTE 
	bool oneormorekey = false; //check to see if key column used
	BTreeIndex btree;
	vector<SelCond> equalitycond; //check for the equality condition
	vector<SelCond> notindexcond;
	const string tablename = table + ".idx";
	if ((rc = btree.open(tablename, 'r')) == 0){ 
		for (i = 0; i < cond.size(); i++){ //check which conditions use the key
			if (cond[i].attr == 1){
				oneormorekey = true; //to make sure we use the index later
				int currentCondVal;
				currentCondVal = atoi(cond[i].value);
				switch (cond[i].comp){
					case SelCond::EQ:
						equalitycond.push_back(cond[i]);
						break;
					case SelCond::NE:
						notindexcond.push_back(cond[i]);
						break;
					case SelCond::LT:
						//currentCondVal = atoi(cond[i].value);
						if ((!maxfirst && !maxfirstGTE) || max >= currentCondVal){
							max = currentCondVal - 1;
							maxfirst = true;
							maxfirstGTE = false;
							maxexist = true;
						}
						break;
					case SelCond::GT:
						//currentCondVal = atoi(cond[i].value);
						if ((!minfirst && !minfirstLTE) || min <= currentCondVal){
							min = currentCondVal + 1;
							minfirst = true;
							minfirstLTE = false;
							minexist = true;
						}
						break;
					case SelCond::LE:
						//currentCondVal = atoi(cond[i].value);
						if ((!maxfirst && !maxfirstGTE) || max > currentCondVal){
							max = currentCondVal;
							maxfirst = false;
							maxfirstGTE = true;
							maxexist = true;
						}
						break;
					case SelCond::GE:
						//currentCondVal = atoi(cond[i].value);
						if ((!minfirst && !minfirstLTE) || min < currentCondVal){
							min = currentCondVal;
							minfirst = false;
							minfirstLTE = true;
							minexist = true;
						}
						break;
                        
				} //end switch
			}//end cond[i].attr = 1
			else{
				//it's a value. 
				notindexcond.push_back(cond[i]);
			}
		}//for loop
        
		if (!oneormorekey){
			goto notusingindex;
		}
		
		if (minexist && maxexist){
			if (max < min){
				count = 0;
				goto countstar;
			}
		}
		int condval;
		int oldequal;
		
		//make sure all conditions on key are consistent. make sure
		//all the equality conditions match up with the max and mins
		//of the other conditions on key
		bool equalitycondtrue = false; //if it's a key equality, then we 
		//only look for keys that are equal to this key.
		//if it's not a key equality, we need to look for keys that are
		//either >= min OR/AND <= max
		if (equalitycond.size() > 0){
			oldequal = atoi(equalitycond[0].value);
			equalitycondtrue = true;
		}
		for (i = 0; i < equalitycond.size(); i++){ 
			condval = atoi(equalitycond[i].value);
			if (maxexist){ //if there exists a maximum
				if (max < condval){ //make sure the equality conds
					count = 0;//are less than the maximum
					goto countstar;
				}		
			}
			if (minexist){
				if (min > condval){
					count = 0;
					goto countstar;
				}
			}
			if (condval != oldequal){ // the equals don't match up. 
                //the query is saying that it wants 2 different equals
				count = 0;
				goto countstar;
			}
		}
		
		count = 0;
		if (!minexist){
			min = 0;
			minexist = true;
			minfirstLTE = true;
		}
		
		if (equalitycond.size() > 0){ //if there's a key equality, then
            //all min and max should be that number
			min = atoi(cond[0].value); 
			max = atoi(cond[0].value);
			minfirst = true;
			maxfirst = true;
			minexist = true;
			maxexist = true;
			minfirstLTE = true;
			maxfirstGTE = true;
		}		
		
		int readKey;
		IndexCursor keyIndCur;
		RecordId keyRecordId;
		if ((rc = btree.locate(min, keyIndCur)) < 0)
			return rc;
		btree.readForward(keyIndCur, readKey, keyRecordId);
		//locate returns the first entry that is larger than or
		//equal to the min right now
		/*
         if (minfirst == false)
         while (readKey == min){
         btree.readForward(keyIndCur, readKey, keyRecordId);
         }
         */
		if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
			return rc;
		}
		
		while (!maxexist || (max >= readKey && maxfirstGTE) || (max > readKey && maxfirst)){
			
			if ((rc = rf.read(keyRecordId, key, value)) < 0) {
				goto exit_select;
		    }
            
			// check the conditions on the tuple
			for (int i = 0; i < notindexcond.size(); i++) {
			    // compute the difference between the tuple value and the condition value
				switch (notindexcond[i].attr) {
			        case 1:
                        diff = key - atoi(notindexcond[i].value);
                        break;
			        case 2:
                        diff = strcmp(value.c_str(), notindexcond[i].value);
                        break;
			    }
                
                // skip the tuple if any condition is not met
				switch (notindexcond[i].comp) {
			        case SelCond::EQ:
                        if (diff != 0) goto next_record;
			          	break;
			        case SelCond::NE:
                        if (diff == 0) goto next_record;
			          	break;
			        case SelCond::GT:
                        if (diff <= 0) goto next_record;
			          	break;
			        case SelCond::LT:
                        if (diff >= 0) goto next_record;
			          	break;
			        case SelCond::GE:
                        if (diff < 0) goto next_record;
			          	break;
			        case SelCond::LE:
                        if (diff > 0) goto next_record;
			          	break;
			    }
			} //end for loop
            
            // the condition is met for the tuple. 
            // increase matching tuple counter
            count++;
            
            // print the tuple 
            switch (attr) {
                case 1:  // SELECT key
			        fprintf(stdout, "%d\n", key);
			        break;
                case 2:  // SELECT value
			        fprintf(stdout, "%s\n", value.c_str());
			        break;
                case 3:  // SELECT *
			        fprintf(stdout, "%d '%s'\n", key, value.c_str());
			        break;
            }
            
        next_record:
            if (keyIndCur.pid == -1) {
                break;
            }
            rc = btree.readForward(keyIndCur, readKey, keyRecordId);
		} //while loop
		goto countstar;
	} //tree exists
	
	
    
    
    
notusingindex:
    // open the table file
    if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
        fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
        return rc;
    }
    
    // scan the table file from the beginning
    rid.pid = rid.sid = 0;
    count = 0;
    while (rid < rf.endRid()) {
        // read the tuple
        if ((rc = rf.read(rid, key, value)) < 0) {
            fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
            goto exit_select;
        }
        
        // check the conditions on the tuple
        for (unsigned i = 0; i < cond.size(); i++) {
            // compute the difference between the tuple value and the condition value
            switch (cond[i].attr) {
                case 1:
                    diff = key - atoi(cond[i].value);
                    break;
                case 2:
                    diff = strcmp(value.c_str(), cond[i].value);
                    break;
            }
            
            // skip the tuple if any condition is not met
            switch (cond[i].comp) {
                case SelCond::EQ:
                    if (diff != 0) goto next_tuple;
                    break;
                case SelCond::NE:
                    if (diff == 0) goto next_tuple;
                    break;
                case SelCond::GT:
                    if (diff <= 0) goto next_tuple;
                    break;
                case SelCond::LT:
                    if (diff >= 0) goto next_tuple;
                    break;
                case SelCond::GE:
                    if (diff < 0) goto next_tuple;
                    break;
                case SelCond::LE:
                    if (diff > 0) goto next_tuple;
                    break;
            }
        }
        
        // the condition is met for the tuple. 
        // increase matching tuple counter
        count++;
        
        // print the tuple 
        switch (attr) {
            case 1:  // SELECT key
                fprintf(stdout, "%d\n", key);
                break;
            case 2:  // SELECT value
                fprintf(stdout, "%s\n", value.c_str());
                break;
            case 3:  // SELECT *
                fprintf(stdout, "%d '%s'\n", key, value.c_str());
                break;
        }
        
        // move to the next tuple
    next_tuple:
        ++rid;
    }
    
    // print matching tuple count if "select count(*)"
countstar:
    if (attr == 4) {
        fprintf(stdout, "%d\n", count);
    }
    rc = 0;
    
    // close the table file and return
exit_select:
    rf.close();
    btree.close();
    return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index){
    /* your code here */
	RecordFile rf;   // RecordFile containing the table
	RC rc;
	if ((rc = rf.open(table + ".tbl", 'w')) < 0) {
	    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
	    return rc;
	} //error open table
    
    // Get file name into c string
    char* cstr = new char[loadfile.size()+1];
    strcpy(cstr,loadfile.c_str());
    
    ifstream file(cstr);
	if (!file.is_open()){
		fprintf(stderr, "Error: Could not open file\n");
		rf.close();
		file.close();
		return RC_FILE_OPEN_FAILED;
	} //error open file
	
    // BTREE INDEX CREATION
	BTreeIndex btree; //needs to be initialized outside else it's destructed
	if (index){ //index is true
		if ((rc = btree.open(table + ".idx", 'w')) < 0) {
		    fprintf(stderr, "Error: Could not open index file");
			return rc;
		}
	} //end index is true
    
	// RECORD FILE LOADING
	string line;
	int key;     
	string value;
    
  	while (file.good()){
		getline(file, line);
    	rc = parseLoadLine(line, key, value);
		if (!file || rc < 0){ //if either getline or parseLoadLine have an error
			rf.close();
			file.close();
			if (index){
				btree.close();
		    }
			return RC_FILE_READ_FAILED;
		}
		RecordId newRid; //append function outputs newRid
		rc = rf.append(key, value, newRid);//now we have newRid
		if (rc < 0) {//check if append failed
		    fprintf(stderr, "Error: Could not append correctly");
			rf.close();
			file.close();
			if (index){
				btree.close();
		    }
			return rc;
		}//error append
        
        // Populate BTREE with entries
        if (index){
            if ((rc = btree.insert(key, newRid)) < 0){
                fprintf(stderr, "Error: %i Could not write index correctly\n", rc);
                rf.close();
                file.close();
                btree.close();
                return rc;
            }
        }
        
    	//cout << line << endl;
    }//while loop
    
	//everything worked out, so close everything now
    file.close();
	rf.close();
	if (index){
		btree.close();
	}
	
  	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }
    
    // get the integer key value
    key = atoi(s);
    
    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }
    
    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }
    
    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }
    
    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }
    
    return 0;
}
